package de.loopmode.containers
{
	import de.loopmode.display.RectangleShape;
	import de.loopmode.events.ResizableTitleWindowEvent;
	
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	
	import mx.containers.TitleWindow;
	import mx.core.Application;
	import mx.core.UIComponent;
	import mx.managers.CursorManager;
	import mx.managers.PopUpManager;
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;

	[Event(name="resizeBegin",type="com.stoneagekings.events.PopupEvent")]	
	[Event(name="resize",type="com.stoneagekings.events.PopupEvent")] 	
	[Event(name="resizeEnd",type="com.stoneagekings.events.PopupEvent")]	
	[Event(name="dragBegin",type="com.stoneagekings.events.PopupEvent")]	
	[Event(name="drag",type="com.stoneagekings.events.PopupEvent")]	
	[Event(name="dragEnd",type="com.stoneagekings.events.PopupEvent")]
	
	[Style(name="tempRectangleColor", type="uint", format="Number", inherit="no")]
	[Style(name="tempRectangleAlpha", type="Number", inherit="no")]
	
	public class ResizableTitleWindow extends TitleWindow
	{		
		private var main:Application;
		 		 
		private var resizeEdgeTolerance:Number = 3;
		private var resizeCornerTolerance:Number = 6;	
		
		private var _isResizing:Boolean = false;		 
		private var _isResizable:Boolean = true;	
		private var _liveResizing:Boolean = true; 	
		
		private var _tempRectangleStyleChanged:Boolean;
		
		private var currentCursorClass:Class;
		private var initialWidth:Number;
		private var initialHeight:Number;
		private var initialX:Number;
		private var initialY:Number;
		private var initialMouseX:Number;
		private var initialMouseY:Number;		 
		
		private var mouseChildrenCachedValue:Boolean;		
		private var temporaryRectangleShape:UIComponent;		
		private var startResizingOnMouseDownFlag:Boolean;  
		
		private static var activePopup:ResizableTitleWindow;
		private static var styleClassConstructed:Boolean;				 
		 
		public function ResizableTitleWindow()
		{
			super();
			this.main = Application.application as Application;  
			this.registerEvents();
			ResizableTitleWindow.activePopup = this;
			if (!ResizableTitleWindow.styleClassConstructed) {
				ResizableTitleWindow.constructStyle();
				ResizableTitleWindow.styleClassConstructed = true;
			}
		}  
		
		/**
		 * Registers events used by the class. 
		 * Fired within the constructor - can not access UI children.
		 */
		public function registerEvents():void
		{			  
			this.addEventListener(Event.ADDED_TO_STAGE, this.addedToStageHandler, false, 0, true);
		} 
		
		/**
		 * When stage is available
		 */
		private function addedToStageHandler(event:Event):void
		{
			this.registerUIEvents()
		}
		
		/**
		 * Registers events after being added to stage. 
		 */
		public function registerUIEvents():void
		{		
			// listen for ESC key to close popup
			this.stage.addEventListener(KeyboardEvent.KEY_DOWN, this.stageKeyDownHandler, false, 0, true);	  
			this.stage.addEventListener(MouseEvent.MOUSE_MOVE, this.stageMouseMoveHandler, false, 0, true);  
			this.stage.addEventListener(MouseEvent.MOUSE_UP, this.stageMouseUpHandler, false, 0, true);
			this.stage.addEventListener(MouseEvent.MOUSE_DOWN, this.stageMouseDownHandler, false, 0, true); 
			
		} 
		
		/**
		 * Removes events registered by the class.
		 * called in closeHandler()
		 */
		public function removeEvents():void
		{ 
			if (this.stage) {
				this.stage.removeEventListener(KeyboardEvent.KEY_DOWN, this.stageKeyDownHandler);
				this.stage.removeEventListener(MouseEvent.MOUSE_MOVE, this.stageMouseMoveHandler);
				this.stage.removeEventListener(MouseEvent.MOUSE_UP, this.stageMouseUpHandler);
				this.stage.removeEventListener(MouseEvent.MOUSE_DOWN, this.stageMouseDownHandler); 
			} 
			this.removeEventListener(Event.ADDED_TO_STAGE, this.addedToStageHandler); 
		}
		
		private function stageKeyDownHandler(event:KeyboardEvent):void
		{
			if (this != ResizableTitleWindow.activePopup) {
				return;
			}
			
			if (!this.visible || !this.includeInLayout) {
				return;
			}
			
	 		// capture ESC key
			if (event.keyCode == 27) {
				this.close();
			}		
		}
		 
		
		
		
		[Bindable(event="isResizableChanged")]
		public function get isResizable():Boolean
		{
			return this._isResizable; 
		}
		public function set isResizable(value:Boolean):void
		{
			this._isResizable = value;
			if (this.isResizing) { 
				this.stopResizing()
			}
			this.dispatchEvent( new Event("isResizableChanged") );
		}
		
		
		
		
		[Bindable(event="liveResizingChanged")]
		public function get liveResizing():Boolean
		{
			return this._liveResizing; 
		}
		public function set liveResizing(value:Boolean):void
		{
			this._liveResizing = value;
			this.dispatchEvent( new Event("liveResizingChanged") );
		}
		
		
		
		[Bindable(event="resizeBegin")]
		[Bindable(event="resizeEnd")]
		public function get isResizing():Boolean
		{
			return this._isResizing;
		}
		
		
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		// mouse events 
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~	
	  
	 
		private function stageMouseDownHandler(event:MouseEvent):void
		{ 
			var mouse_hit:Boolean = this.hitTestPoint(event.stageX, event.stageY);
			if (!mouse_hit) {
				return;
			}
			
			ResizableTitleWindow.activePopup = this;
			
			if (this.isResizable) {
				if (this.startResizingOnMouseDownFlag) {
					this.startResizing(event);
					event.stopImmediatePropagation();
					event.stopPropagation();
					return;
				}
			}
			 
			event.stopPropagation(); 
		}
		 
		
		private function stageMouseUpHandler(event:MouseEvent):void
		{	
			
			if (!this.visible || !this.includeInLayout) {
				return;
			}
			 		
			if (this.isResizing) {
				this.stopResizing(event);
			}	
		}
	 
		/**
		 * Listens to mouse movements on the main stage.
		 * Simulates rollOver / rollOut for resizing purposes.
		 * 
		 * In case of resizing, we use stage mouse movements instead of rollOver/rollOut or mouseOver/mouseOut, 
		 * because it allows better control of what to do when the user moves his mouse outside of the popup.
		 * rollOut / mouseOut events are only fired when the user actually moves the mouse from inside of the popup
		 * out of it, but things can happen that prevent those events from being fired, for example when 
		 * the mouse movement was too quick, or our app lost focus due to multitasking.
		 * So we use the stage mouseMove event instead.
		 */
		private function stageMouseMoveHandler(event:MouseEvent):void
		{
			if (this != ResizableTitleWindow.activePopup) {
				return;
			}
			
			if (!this.visible || !this.includeInLayout) {
				return;
			}
			
			// is the users mouse above this popup?
			var mouse_hit:Boolean = this.hitTestPoint(event.stageX, event.stageY);
			
			if (this.isResizable) {
					
				if (this.isResizing) {
					this.resizeHandler(event);
					return;
				}
								
				if (mouse_hit) { 
					this.updateResizeCursor(event);	
				} 
				else { 
					if (this.currentCursorClass!=null) {	 
			 			CursorManager.removeAllCursors(); 		
			 			this.currentCursorClass = null;
			 			this.startResizingOnMouseDownFlag = false;		
			 		} 
				}
			}
			 
		}
		
		
		 
		
		
		
			
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		//
		// resizing
		//
		//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		
		 
		/**
		 * Handles mouse movements within the popup.
		 * If the popup is resizable, and the mouse is close to any edge of the popup,
		 * a special mouse cursor will be shown, and a flag will be set, so that when the user presses the mouse,
		 * resizing can start.
		 */
		private function updateResizeCursor(event:MouseEvent):void
		{
			
			if (ResizableTitleWindow.activePopup != this) {
				return;
			}
			
			var current_cursor_class:Class = this.currentCursorClass;			
			var cursor_class:Class;
			
 			
 			var mouse_x:Number = this.mouseX;
 			var mouse_y:Number = this.mouseY;
 			
 			
 			
 			// check whether we are close enough to any edge or corner:
 			var show_w:Boolean = mouse_x <= this.resizeEdgeTolerance; 			
 			var show_e:Boolean = mouse_x >= this.width - this.resizeEdgeTolerance;
 			
 			var show_n:Boolean = mouse_y <= this.resizeEdgeTolerance;
 			var show_s:Boolean = mouse_y >= this.height - this.resizeEdgeTolerance;
 			
 			var show_nw:Boolean = mouse_x <= this.resizeCornerTolerance && mouse_y <= this.resizeCornerTolerance;
 			var show_ne:Boolean = mouse_x >= this.width - this.resizeCornerTolerance && mouse_y <= this.resizeCornerTolerance;
 			
 			var show_sw:Boolean = mouse_x <= this.resizeCornerTolerance && mouse_y >= this.height - this.resizeCornerTolerance;
 			var show_se:Boolean = mouse_x >= this.width - this.resizeCornerTolerance && mouse_y >= this.height - this.resizeCornerTolerance;
 			
 			
 			
 			
 			// if we are not near any edge or border to allow resizing, we clean up and jump out.
 			if (!show_w && !show_n && !show_e && !show_s && !show_nw && !show_ne && !show_se && !show_sw) { 	
 				this.startResizingOnMouseDownFlag = false;		
 				this.currentCursorClass = null;	
 				CursorManager.removeAllCursors();
 				return;
 			} 
 		 
 			// if we arrive here, it means that we are close enough to some corner or edge to allow resizing.
 			this.startResizingOnMouseDownFlag = true;		
 			
 			
 			// find out which cursor to show, and show it:
 			
 			//--------------------------------------
 			// set vertical only or horizontal only cursor as needed
 			//--------------------------------------
 			
 			if ( show_w && !show_n && !show_s) {
 				cursor_class = ResizeCursorW;
 			} 	  				
 			
 			if ( show_n && !show_w && !show_e) {
 				cursor_class = ResizeCursorN;
 			}
 			
 			if ( show_s && !show_w && !show_e) {
 				cursor_class = ResizeCursorS;
 			} 				
 			
 			if ( show_e && !show_n && !show_s) {
 				cursor_class = ResizeCursorE;
 			} 	  
 			
 			
 			//--------------------------------------
 			// set diagonal cursors for corners as needed
 			//--------------------------------------
 			 
 			if (show_ne) {
 				cursor_class = ResizeCursorNE;
 			}
 			
 			if (show_nw) {
 				cursor_class = ResizeCursorNW;
 			}
 			
 			if (show_sw) {
 				cursor_class = ResizeCursorSW;
 			}
 			
 			if (show_se) {
 				cursor_class = ResizeCursorSE;
 			}
 			
 			
 			
 			 
 			// if our needed cursor changes, remove the old one to avoid flickering between the two types.
 			// this happens for example when you reach a corner of teh popup after you followed an edge with the mouse. 
 			if (cursor_class != current_cursor_class) {
 			 	CursorManager.removeAllCursors();		 			 
 			}
 			
 		
 			// okay, we know which cursor we need to show, so lets show it
 			//var cursor_class:Class = Assets.getAsset( needed_cursor_type ); 					
 			//var cursor_w:Number = Assets.getImageSize(needed_cursor_type)['width'];
 			//var cursor_h:Number = Assets.getImageSize(needed_cursor_type)['height'];
 			CursorManager.setCursor( cursor_class, 2, -new cursor_class().width/2, -new cursor_class().height/2  );		
 			  
 			  
 			// remember our cursor decision for the next check, because as described above,
 			// we need to react on changes of the cursor type on corners to avoid flickering  
 			this.currentCursorClass = cursor_class;			 
 			 
 			
		}
		 
		
		private function startResizing(event:MouseEvent):void
		{
			this.initialWidth = this.width;
			this.initialHeight = this.height;
			this.initialX = this.x;
			this.initialY = this.y;
			this.initialMouseX = event.stageX;
			this.initialMouseY = event.stageY;
			
			if (this.liveResizing) {
				// nothing special is needed here, when liveResizing is activated.
			} else {
				// without liveresizing, we need a rectangle to resize instead of the real popup
				var rect:UIComponent = this.temporaryRectangleShape;
				if (!rect) {
					rect = this.createTemporaryRectangle();
					this.temporaryRectangleShape = rect; 
				}		
				rect.x = this.initialX;
				rect.y = this.initialY;
				rect.width = this.initialWidth;
				rect.height = this.initialHeight; 
				
				this.parent.addChild(rect);			
		 	}
		  
		  
			this.main.mouseChildren = false;
			this.mouseChildrenCachedValue = this.mouseChildren;
			this.mouseChildren = false; 
			this._isResizing = true;
			
			this.dispatchEvent( new ResizableTitleWindowEvent(ResizableTitleWindowEvent.RESIZE_BEGIN) );
			
		}
		private function resizeHandler(event:MouseEvent):void
		{
			var initial_width:Number = this.initialWidth;
			var initial_height:Number = this.initialHeight;
			var initial_x:Number = this.initialX;
			var initial_y:Number = this.initialY;
			var initial_mouse_x:Number = this.initialMouseX;
			var initial_mouse_y:Number = this.initialMouseY;
			//
			var mouse_x:Number = event.stageX;
			var mouse_y:Number = event.stageY;
			
			if (mouse_x < 0) {
				mouse_x = 0;
			}
			if (mouse_x > this.stage.stageWidth) {
				mouse_x = this.stage.stageWidth;
			}
			
			if (mouse_y < 0) {
				mouse_y = 0;
			}
			if (mouse_y > this.stage.stageHeight) {
				mouse_y = this.stage.stageHeight;
			}
			
			//
			var new_w:Number = initial_width;
			var new_h:Number = initial_height;
			var new_x:Number = this.x;
			var new_y:Number = this.y;
			//
			
			switch (this.currentCursorClass) {
				
				case ResizeCursorN:
					new_h = initial_height + (initial_mouse_y - mouse_y);
					new_y = mouse_y;
					break;
				
				case ResizeCursorW:
					new_w = initial_width + (initial_mouse_x - mouse_x);
					new_x = mouse_x;
					break;
					
				case ResizeCursorE:
					new_w = initial_width + (mouse_x - initial_mouse_x);
					break; 
				
				case ResizeCursorS:
					new_h = initial_height + (mouse_y - initial_mouse_y);
					break; 
					
				
				case ResizeCursorNW:
					// N
					new_h = initial_height + (initial_mouse_y - mouse_y);
					new_y = mouse_y;
					// W
					new_w = initial_width + (initial_mouse_x - mouse_x);
					new_x = mouse_x;
					break;
				
				case ResizeCursorNE:
					// N
					new_h = initial_height + (initial_mouse_y - mouse_y);
					new_y = mouse_y;
					// E
					new_w = initial_width + (mouse_x - initial_mouse_x);
					break;  
				
				case ResizeCursorSE:
					// S
					new_h = initial_height + (mouse_y - initial_mouse_y);
					// E
					new_w = initial_width + (mouse_x - initial_mouse_x);
					break;  
					
				case ResizeCursorSW:
					// S
					new_h = initial_height + (mouse_y - initial_mouse_y);
					// W
					new_w = initial_width + (initial_mouse_x - mouse_x);
					new_x = mouse_x;
					break;  
			}
			
			// when resizing from the left or bottom side, and the minimum size is reached, the popup would not
			// become any smaller, but it would still be pushed to the right or top. to avoid this, 
			//we need a flag that min size was reached:
		
			var min_w_reached:Boolean;
			var min_h_reached:Boolean;
			
			if (new_w < this.minWidth) {
				new_w = this.minWidth;
				min_w_reached = true;
			}
			
			if (new_h < this.minHeight) {
				new_h = this.minHeight;
				min_h_reached = true;
			}
			
			// set new size and new position  
			if (this.liveResizing) {
				this.width = new_w
				this.height = new_h				 
				if (!min_w_reached) { 
					this.x = new_x;
				}
				if (!min_h_reached) { 
					this.y = new_y;
				}
			} else {
				this.temporaryRectangleShape.width = new_w
				this.temporaryRectangleShape.height = new_h				 
				if (!min_w_reached) { 
					this.temporaryRectangleShape.x = new_x;
				}
				if (!min_h_reached) { 
					this.temporaryRectangleShape.y = new_y;
				}
			}
			
			
			// resizing done!  
			
			this.dispatchEvent( new ResizableTitleWindowEvent(ResizableTitleWindowEvent.RESIZE) );
			
		}
		
		
		private function stopResizing(event:MouseEvent=null):void
		{
			if (!this.liveResizing) { 				
				this.x = this.temporaryRectangleShape.x;
				this.y = this.temporaryRectangleShape.y;
				this.width = this.temporaryRectangleShape.width;
				this.height = this.temporaryRectangleShape.height; 
			}
			 
			
			// clean up
			this.initialWidth = undefined;
			this.initialHeight = undefined;
			this.initialMouseX = undefined;
			this.initialMouseY = undefined;
			
			CursorManager.removeAllCursors(); 		
 			this.currentCursorClass = null;
 			this.startResizingOnMouseDownFlag = false;	
 			
 			if (!this.liveResizing) { 				
				this.temporaryRectangleShape.parent.removeChild( this.temporaryRectangleShape );
 				this.temporaryRectangleShape = null;
 			}
 			
			this.mouseChildren = this.mouseChildrenCachedValue;
 			this.main.mouseChildren = true;
			this._isResizing = false;
			
			this.dispatchEvent( new ResizableTitleWindowEvent(ResizableTitleWindowEvent.RESIZE_END) );
			
			this.updateResizeCursor(event);
			
		}
	
		  
		public function createTemporaryRectangle():UIComponent
		{
			var rect:RectangleShape = new RectangleShape(); 
			rect.fillAlpha = 0;
			rect.borderColor = Number(this.getStyle('tempRectangleColor'));
			rect.borderAlpha = Number(this.getStyle('tempRectangleAlpha'));
			rect.borderThickness = 1;	
			return rect;	
		}
		
		public function close(event:Event=null):void
		{
			this.removeEvents();
			
			// when close() is called due to the native close button which dispatched a close event, we dont 
			// need to dispatch it again (should also lead to recursive infinite loop?)
			// dispatch the close event for all other cases.
			if (event && event.type != Event.CLOSE) {
				this.dispatchEvent( new ResizableTitleWindowEvent( ResizableTitleWindowEvent.CLOSE ) );
			}
			PopUpManager.removePopUp( this );
		} 
		
		public function centerOnStage(event:Event=null):void
		{
			PopUpManager.centerPopUp(this); 
		}
		
		
		// styling
		private static function constructStyle():Boolean
		{
		    // get style defined for ImageRail
		    var style:CSSStyleDeclaration = 
		        StyleManager.getStyleDeclaration("ResizableTitleWindow");
		 
		    // check to see if there's already an existing 
		    // style declaration for this class
		    if (style) 
		    {
		        // its possible for a style to exist without 
		        // defining all of the possible styles 
		        // in which case we need to check each style
		        // explicitly and set a default if needed
		        if (style.getStyle("tempRectangleColor") == undefined)
		        {
		            style.setStyle("tempRectangleColor", '#CCCCCC');
		        }
		        if (style.getStyle("tempRectangleAlpha") == undefined)
		        {
		            style.setStyle("tempRectangleAlpha",1);
		        }
		    }
		    else // create a default style declaration
		    {
		        style = new CSSStyleDeclaration();
		        style.defaultFactory = function():void
		        {
		            this.tempRectangleColor = 0xCCCCCC;
		            this.tempRectangleAlpha = 1;
		        }
		        // set default style
		        // this will trigger styleChanged
		        StyleManager.setStyleDeclaration("ResizableTitleWindow",style,true);
		    }
		 
		    return true;
		}
		
		 
		
	}
}